﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides methods to deal with Fourier transformations.
    /// </summary>
    [Serializable]
    public static class FourierTransformation
    {
        /// <summary>
        /// A DFT for real numbers.
        /// </summary>
        /// <param name="vector">The vector to transform.</param>
        /// <returns>The result of the transformation in from of an complex array.</returns>
        public static ComplexNumber[] RealDFT(GeneralVector vector)
        {
            return FourierTransformation.RealDFT(vector.VectorData);
        }

        /// <summary>
        /// A DFT for real numbers.
        /// </summary>
        /// <param name="vectorData">The data to transform.</param>
        /// <returns>The result of the transformation in from of an complex array.</returns>
        public static ComplexNumber[] RealDFT(double[] vectorData)
        {
            if (vectorData == (double[]) null)
            {
                throw new ArgumentNullException("vectorData");
            }

            int numberOfSample = vectorData.Length;
            ComplexNumber[] result = new ComplexNumber[numberOfSample];

            for (int k = 0; k < numberOfSample; k++)
            {
                result[k] = new ComplexNumber();

                for (int n = 0; n < numberOfSample; n++)
                {
                    result[k].RealNumberPart += vectorData[n] * Math.Cos((-2 * Math.PI * k * n) / numberOfSample);
                    result[k].ImaginaryNumberPart += vectorData[n] * Math.Sin((-2 * Math.PI * k * n) / numberOfSample);
                }
            }

            return result;
        }

        /// <summary>
        ///  A FFT for real numbers. Uses the Cooley and Tukey algorithm. The array has to
        /// have a length of 2^x.
        /// </summary>
        /// <param name="vectorData">The real number vector data for the FFT.</param>
        /// <returns>The result of the transformation.</returns>
        public static ComplexNumber[] RealFFT(double[] vectorData)
        {
            if (vectorData == (double[]) null)
            {
                throw new ArgumentNullException("vectorData");
            }

            return FourierTransformation.RealFFT(new GeneralVector(vectorData));
        }

        /// <summary>
        /// A FFT for real numbers. Uses the Cooley and Tukey algorithm. The array has to
        /// have a length of 2^x.
        /// </summary>
        /// <param name="vector">The real number vector for the FFT.</param>
        /// <returns>The result of the transformation.</returns>
        public static ComplexNumber[] RealFFT(GeneralVector vector)
        {
            if (vector == (GeneralVector) null)
            {
                throw new ArgumentNullException("vector");
            }

            ComplexNumber[] tempuri = new ComplexNumber[vector.Count];

            for (int i = 0; i < vector.Count; i++)
            {
                tempuri[i] = new ComplexNumber(vector[i]);
            }

            return FourierTransformation.ComplexFFT(tempuri);
        }

        /// <summary>
        /// A FFT for complex numbers. Uses the Cooley and Tukey algorithm. The array has to
        /// have a length of 2^x.
        /// </summary>
        /// <param name="vector">The complex vector for the FFT.</param>
        /// <returns>The result of the transformation.</returns>
        public static ComplexNumber[] ComplexFFT(GeneralComplexVector vector)
        {
            if (vector == (GeneralComplexVector) null)
            {
                throw new ArgumentNullException("vector");
            }

            return FourierTransformation.ComplexFFT(vector.VectorData);
        }


        /// <summary>
        /// A FFT for complex numbers. Uses the Cooley and Tukey algorithm. The array has to
        /// have a length of 2^x.
        /// </summary>
        /// <param name="vectorData">The complex vector for the FFT.</param>
        /// <returns>The result of the transformation.</returns>
        public static ComplexNumber[] ComplexFFT(ComplexNumber[] vectorData)
        {
            if (vectorData == (ComplexNumber[]) null)
            {
                throw new ArgumentNullException("vectorData");
            }

            int n = vectorData.Length;

            if ((n & n - 1) != 0)
            {
                return new ComplexNumber[0];
            }

            if (n == 1)
            {
                return vectorData;
            }

            ComplexNumber[] gPar = new ComplexNumber[n / 2];
            ComplexNumber[] uPar = new ComplexNumber[n / 2];

            for (int k = 0; k != n / 2; k++)
            {
                gPar[k] = vectorData[k * 2];
                uPar[k] = vectorData[k * 2 + 1];
            }

            ComplexNumber tempuri;
            ComplexNumber[] vectorG = ComplexFFT(gPar);
            ComplexNumber[] vectorU = ComplexFFT(uPar);
            ComplexNumber[] vectorR = new ComplexNumber[n];

            for (int k = 0; k != n / 2; k++)
            {
                double x = (-2.0 * Math.PI * k) / n;

                tempuri = new ComplexNumber(Math.Cos(x), Math.Sin(x));
                vectorR[k] = vectorG[k] + (vectorU[k] * tempuri);
                vectorR[k + (n / 2)] = vectorG[k] - (vectorU[k] * tempuri);
            }

            return vectorR;
        }

        /// <summary>
        /// Extracts the magnitudes of DFT or FFT result.
        /// </summary>
        /// <param name="data">The array of complex numbers, which are the result of the DFT or FFT.</param>
        /// <returns>An array of numbers, which represents the length of the magnitudes.</returns>
        public static double[] MagnitudeExtraction(ComplexNumber[] data)
        {
            if (data == (ComplexNumber[]) null)
            {
                throw new ArgumentNullException("data");
            }

            double[] result = new double[data.Length];

            for (int i = 0; i < data.Length; i++)
            {
                result[i] = Math.Sqrt(Math.Pow(data[i].RealNumberPart, 2) + Math.Pow(data[i].ImaginaryNumberPart, 2));
            }

            return result;
        }

        /// <summary>
        /// Extracts the phases of DFT or FFT result.
        /// </summary>
        /// <param name="data">The array of complex numbers, which are the result of the DFT or FFT.</param>
        /// <returns>An array of numbers, which represents the phases.</returns>
        public static double[] PhaseExtraction(ComplexNumber[] data)
        {
            if (data == (ComplexNumber[]) null)
            {
                throw new ArgumentNullException("data");
            }

            double[] result = new double[data.Length];

            for (int i = 0; i < data.Length; i++)
            {
                result[i] = Math.Atan2(data[i].ImaginaryNumberPart, data[i].RealNumberPart);
            }

            return result;
        }
    }
}